import numpy
# scipy.special for the sigmoid function expit()
import scipy.special
import os
# 神经网络class定义
class neuralNetwork():
    
    # 初始化神经网络必须的参数（构造函数）
    def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
        
        # 设置输入、隐藏、输出层的节点数
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes
        
        # 随机初始化链接权重矩阵，wih与who
        # 矩形数组中的权重是w_i_j，即从i节点到下一层的j节点的链接
        # w11 w21
        # w12 w22 etx
        # normal表示以正太分布取样，其中均值为0，标准方差为节点传入链接数目的开方。
        # (self.hnodes, self.inodes)表示初始化矩阵的行列数
        self.wih = numpy.random.normal(0.0, pow(self.hnodes, -0.5), (self.hnodes, self.inodes))#200*784
        self.who = numpy.random.normal(0.0, pow(self.onodes, -0.5), (self.onodes, self.hnodes))#10*200
        # 学习率
        self.lr = learningrate
        # 激活函数是sigmoid函数
        self.activation_function = lambda x: scipy.special.expit(x)
        pass

    # 对神经网络进行训练
    def train(self, inputs_list, targets_list):
        
        # 将长为(784,)的inputs_list列表，转换为(1，784)的2维数组，再转置成(784,1)
        inputs = numpy.array(inputs_list, ndmin = 2).T
        targets = numpy.array(targets_list, ndmin = 2).T

        # 计算进入隐藏层的信号(200,784)dot(784,1)=(200,1)
        hidden_inputs = numpy.dot(self.wih, inputs)
        # 计算隐藏层激活函数发出的信号
        hidden_outputs = self.activation_function(hidden_inputs)
        
        # 计算进入输出层的信号(10,200)dot(200,1)=(10,1)
        final_inputs = numpy.dot(self.who, hidden_outputs)
        print(final_inputs)
        # 计算输出层激活函数发出的信号(10,1)
        final_outputs = self.activation_function(final_inputs)
        
        # 输出层误差为(target - actual)=(10,1)-(10,1)
        output_errors = targets - final_outputs
        # 隐藏层误差为按权重比例分割输出层误差后，在隐藏层节点的重组值(200,10) dot (10,1)=(200,1)
        hidden_errors = numpy.dot(self.who.T, output_errors)
        
        # 更新隐藏层与输出层之间权重，隐藏层与输出层之间梯度=EoxSo(1-So)dotOh
        # =[输出层误差X激活输出X(1-激活输出)]*[隐藏层输出]=(10,1)*(10,1)*[1-(10,1)]dot(200,1).T=(10,1)dot(1,200)=(10,200)
        self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs))
        # 更新输入层与隐藏层之间权重，输入层与隐藏城之间梯度=EhxSh(1-Sh)dotOi
        # =[输出层误差X激活输出X(1-激活输出)]*[隐藏层输出]=(200,1)*(200,1)*[1-(200,1)]dot(1,784)=(200,784)
        self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs))
        pass
    
    # 对神经网络进行查询
    def query(self, inputs_list):
        # 将一维(784,)列表inputs_list转换为2维数组(1,784)再转置为(784,1)
        inputs = numpy.array(inputs_list, ndmin = 2).T
        # 计算进入隐藏层的信号(200,784).(784,1)=(200,1)
        hidden_inputs = numpy.dot(self.wih, inputs)
        # 计算隐藏层发出的信号(200,1)
        hidden_outputs = self.activation_function(hidden_inputs)
        # 计算进入输出层的信号(10,200).(200,1)
        final_inputs = numpy.dot(self.who, hidden_outputs)
        # 计算输出层发出的信号(10,1)
        final_outputs = self.activation_function(final_inputs)
        return final_outputs




# 训练网络
def trainNet(input_nodes, hidden_nodes, output_nodes, learning_rate, training_data_list, epochs=2):
    # 实例化一个神经网络的实例
    model = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
    count = 0
    # 对网络循环训练epochs个周期
    for e in range(epochs):
        # 循环遍历训练集，训练网络
        for record in training_data_list:

            # 初始化输入向量，对输入值进行缩放和移位
            # 保证输入值在(0.01,1)范围，避免网络过饱和。加0.01保证节点输入不为0，避免产生死节点
            all_values = record.split(',')
            inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01

            # 初始化输出向量，输出为0-9中的一个数字，所以向量维度为10
            # all_values[0]是标签在输出向量中对应的位置
            # 除了输出标签所对应的向量位置是0.99，其余全部为0.01
            targets = numpy.zeros(output_nodes) + 0.01
            targets[int(all_values[0])] = 0.99

            # 用输入、输出数据训练网络
            model.train(inputs, targets)

            count +=1
            if(count%1000 == 0):
                print(count)

            pass
        pass
    return model


#
def testNet(model, test_data_list):
    # 记录正确率
    scorecard = []
    # 遍历测试数据集中的所有记录
    for record in test_data_list:
        # 初始化输入向量，对输入值进行缩放和移位
        # 保证输入值在(0.01,1)范围，避免网络过饱和。加0.01保证节点输入不为0，避免产生死节点
        all_values = record.split(',')
        inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01

        # 获取每张图对应的真实数字
        correct_label = int(all_values[0])
        
        # 用训练好的网络进行预测，数组中最大值对应的索引作为预测结果
        outputs = model.query(inputs)
        label = numpy.argmax(outputs)
        
        # 对比预测数字和真实数字是否相同，正确记1，错误记0
        if (label == correct_label):
            scorecard.append(1)
        else:
            scorecard.append(0)
            pass
        pass

    # 求和计算得分
    scorecard_array = numpy.asarray(scorecard)
    print("performance =", scorecard_array.sum() / scorecard_array.size)

#print(numpy.random.rand(4,3))
#print(numpy.random.random_integers(0,100,7))
#print(numpy.random.randint(0,7,(3,3)))
# 输入、隐藏、输出层的节点数，学习率
input_nodes = 784
hidden_nodes = 200
output_nodes = 10
learning_rate = 0.1

# 加载训练数据
training_data_file = open(r"mnist_train_100.csv", 'r')
# training_data_file = open(os.path.dirname(os.path.dirname(os.getcwd()))+"\BigDataFile\mnist_train.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()

model = trainNet(input_nodes, hidden_nodes, output_nodes, learning_rate, training_data_list, 5)




# 加载测试数据
test_data_file = open(r"mnist_test_10.csv", 'r')
test_data_list = test_data_file.readlines()
test_data_file.close()

testNet(model, test_data_list)






























def delete():
    # 输入、隐藏、输出层的节点数
    input_nodes = 784
    hidden_nodes = 200
    output_nodes = 10

    # 学习率
    learning_rate = 0.1

    # 创建神经网络的一个实例
    n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)

    # 加载书写数字识别的训练数据CSV文件为一个列表
    training_data_file = open(r"mnist_train_100.csv", 'r')
    # training_data_file = open(os.path.dirname(os.path.dirname(os.getcwd()))+"\BigDataFile\mnist_train.csv", 'r')
    training_data_list = training_data_file.readlines()
    training_data_file.close()


    # 定义训练周期次数
    epochs = 2
    count = 0

    # 对网络训练epochs个周期
    for e in range(epochs):
        # 循环遍历训练集，训练网络
        for record in training_data_list:

            # 初始化输入向量，对输入值进行缩放和移位
            # 保证输入值在(0.01,1)范围，避免网络过饱和。加0.01保证节点输入不为0，避免产生死节点
            all_values = record.split(',')
            inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01

            # 初始化输出向量，输出为0-9中的一个数字，所以向量维度为10
            # all_values[0]是标签在输出向量中对应的位置
            # 除了输出标签所对应的向量位置是0.99，其余全部为0.01
            targets = numpy.zeros(output_nodes) + 0.01
            targets[int(all_values[0])] = 0.99

            # 用输入输出数据训练网络
            n.train(inputs, targets)

            count +=1
            if(count%1000 == 0):
                print(count)

            pass
        pass



    # 加载数字识别的测试数据CSV文件为一个列表
    test_data_file = open(r"mnist_test_10.csv", 'r')
    test_data_list = test_data_file.readlines()
    test_data_file.close()



    # 评价网络运转的良好程度的计分板，初始化为空
    scorecard = []

    # 遍历测试数据集中的所有记录
    for record in test_data_list:
        
        # 在','逗号处分割记录
        all_values = record.split(',')
        
        # 正确答案是第一个值
        correct_label = int(all_values[0])
        # print(correct_label, "correct label")
        
        # 对输入值进行缩放和移位
        inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
        
        # 查询网络
        outputs = n.query(inputs)
        
        # 最大值的索引对应的标签
        label = numpy.argmax(outputs)
        # print(label, "network's answer")
        
        # 添加正确(1)或不正确(0)到列表中
        if (label == correct_label):
            # 网络的答案匹配正确的答案，计分板加1
            scorecard.append(1)
        else:
            # 网络的答案不匹配正确的答案，计分板加0
            scorecard.append(0)
            pass
        pass

    # 计算性能分数，即正确答案的得分
    scorecard_array = numpy.asarray(scorecard)
    print("performance =", scorecard_array.sum() / scorecard_array.size)
